#define _USE_MATH_DEFINES
#include <math.h>
#include <limits.h>
#include "ray.h"
#include "player.h"
#include "map.h"
#include "defs.h"
#include "graphics.h"

ray_t rays[NUM_RAYS];

float distanceBetween(float x1, float y1, float x2, float y2) {
  return (float)sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
}

void normaliseAngle(float* angle) {
  *angle = (float)fmod((double)*angle, (double)2*M_PI);
  if(*angle < 0)
    *angle = 2*M_PI + *angle;
}

void castRay(float rayAngle, int id) {
  float xintercept, yintercept, xstep, ystep;
  int isRayFacingDown, isRayFacingUp, isRayFacingRight, isRayFacingLeft;
  int foundHorizWallHit = 0;
  float horizWallHitX = 0;
  float horizWallHitY = 0;
  int horizWallContent = 0;
  float nextHorizTouchX;
  float nextHorizTouchY;
  float xToCheck;
  float yToCheck;
  int foundVertWallHit = 0;
  float vertWallHitX = 0;
  float vertWallHitY = 0;
  int vertWallContent = 0;
  float nextVertTouchX;
  float nextVertTouchY;
  float horizHitDistance;
  float vertHitDistance;

  normaliseAngle(&rayAngle);
  isRayFacingDown = (rayAngle > 0 && rayAngle < M_PI);
  isRayFacingUp = !isRayFacingDown;
  isRayFacingRight = (rayAngle > M_PI_2 * 3 || rayAngle < M_PI_2);
  isRayFacingLeft = !isRayFacingRight;
  
  yintercept = floor(player.y / TILE_SIZE) * TILE_SIZE;
  yintercept += isRayFacingDown ? TILE_SIZE : 0;
  
  xintercept = player.x + (yintercept - player.y) / tan(rayAngle);
  
  // Calculate xstep ystep
  ystep = TILE_SIZE;
  ystep *= isRayFacingUp ? -1 : 1;
  
  xstep = TILE_SIZE / tan(rayAngle);
  xstep *= (isRayFacingLeft && xstep > 0) ? -1 : 1;
  xstep *= (isRayFacingRight && xstep < 0) ? -1 : 1;
  
  nextHorizTouchX = xintercept;
  nextHorizTouchY = yintercept;
  
  while(isInsideMap(nextHorizTouchX, nextHorizTouchY)) {
    xToCheck = nextHorizTouchX;
    yToCheck = nextHorizTouchY + (isRayFacingUp ? -1 : 0);
    
    if(mapHasWallAt(xToCheck, yToCheck)) {
      // this is a wall hit
      horizWallHitX = nextHorizTouchX;
      horizWallHitY = nextHorizTouchY;
      horizWallContent = getMapAt((int)floor(yToCheck / TILE_SIZE),(int)floor(xToCheck / TILE_SIZE));
      foundHorizWallHit = 1;
      break;
    } else {
      // increment
      nextHorizTouchX += xstep;
      nextHorizTouchY += ystep;
    }
  }
  
    // Vertical
    xintercept = floor(player.x / TILE_SIZE) * TILE_SIZE;
    xintercept += isRayFacingRight ? TILE_SIZE : 0;
  
    yintercept = player.y + (xintercept - player.x) * tan(rayAngle);
  
    // Calculate xstep ystep
    xstep = TILE_SIZE;
    xstep *= isRayFacingLeft ? -1 : 1;
  
    ystep = TILE_SIZE * tan(rayAngle);
    ystep *= (isRayFacingUp && ystep > 0) ? -1 : 1;
    ystep *= (isRayFacingDown && ystep < 0) ? -1 : 1;
  
    nextVertTouchX = xintercept;
    nextVertTouchY = yintercept;
  
    while(isInsideMap(nextVertTouchX, nextVertTouchY)) {
      xToCheck = nextVertTouchX + (isRayFacingLeft ? -1 : 0);
      yToCheck = nextVertTouchY;

      if(mapHasWallAt(xToCheck, yToCheck)) {
        // this is a wall hit
        vertWallHitX = nextVertTouchX;
        vertWallHitY = nextVertTouchY;
        vertWallContent = getMapAt((int)floor(yToCheck / TILE_SIZE),(int)floor(xToCheck / TILE_SIZE));
        foundVertWallHit = 1;
        break;
      } else {
        // increment
        nextVertTouchX += xstep;
        nextVertTouchY += ystep;
      }
    }
  
  // choose the clostest one
  
  horizHitDistance = foundHorizWallHit
  ? distanceBetween(player.x, player.y, horizWallHitX, horizWallHitY)
  : (float)INT_MAX;
  
  vertHitDistance = foundVertWallHit
  ? distanceBetween(player.x, player.y, vertWallHitX, vertWallHitY)
  : (float)INT_MAX;

  if(vertHitDistance < horizHitDistance) {
    rays[id].distance = vertHitDistance;
    rays[id].wallHitX = vertWallHitX;
    rays[id].wallHitY = vertWallHitY;
    rays[id].wallHitContent = vertWallContent;
    rays[id].wasHitVert = 1;
  } else {
    rays[id].distance = horizHitDistance;
    rays[id].wallHitX = horizWallHitX;
    rays[id].wallHitY = horizWallHitY;
    rays[id].wallHitContent = horizWallContent;
    rays[id].wasHitVert = 0;
  }
  rays[id].rayAngle = rayAngle;
}

void castAllRays() {
  int stripId;
  for(stripId = 0; stripId < NUM_RAYS; stripId++) {
    float rayAngle = player.rotationAngle + atan((stripId - NUM_RAYS/2) / distProjPlane);
    castRay(rayAngle, stripId);
  }
}

void renderRays() {
  int i;
  //  SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
  for(i=0; i<NUM_RAYS; i++) {
    // SDL_RenderDrawLine(renderer, player.x * MINIMAP_SCALE_FACTOR, player.y * MINIMAP_SCALE_FACTOR, rays[i].wallHitX* MINIMAP_SCALE_FACTOR, rays[i].wallHitY* MINIMAP_SCALE_FACTOR);
    draw_line(player.x * MINIMAP_SCALE_FACTOR, player.y * MINIMAP_SCALE_FACTOR, rays[i].wallHitX* MINIMAP_SCALE_FACTOR, rays[i].wallHitY* MINIMAP_SCALE_FACTOR);
  }
}
